/**
 * @brief NAND Flash 驱动
 * @author 张勇 / 2020-05-13
 */

#include "share/unios.h"
#include "_mcu_nand.h"

#include "share/log.h"
#define LOGLEVEL LOG_DEBUG
#define LOGTAG   "NAND"
#include "share/log4app.h"


/******************************************************************************************/
/* 引脚 定义 */

#define NAND_RB_GPIO_PORT               GPIOD
#define NAND_RB_GPIO_PIN                GPIO_PIN_6
#define NAND_RB_GPIO_CLK_ENABLE()       do{ __HAL_RCC_GPIOD_CLK_ENABLE(); }while(0)      /* PD口时钟使能 */

/******************************************************************************************/

/* IO操作函数 */
#define NAND_RB         HAL_GPIO_ReadPin(NAND_RB_GPIO_PORT, NAND_RB_GPIO_PIN)            /* NAND Flash的闲/忙引脚 */

#define NAND_MAX_PAGE_SIZE          4096        /* 定义NAND FLASH的最大的PAGE大小（不包括SPARE区），默认4096字节 */
#define NAND_ECC_SECTOR_SIZE        512         /* 执行ECC计算的单元大小，默认512字节 */

/* NAND FLASH操作相关延时函数 */
#define NAND_TADL_DELAY             30          /* tADL等待延迟,最少70ns */
#define NAND_TWHR_DELAY             25          /* tWHR等待延迟,最少60ns */
#define NAND_TRHW_DELAY             35          /* tRHW等待延迟,最少100ns */
#define NAND_TPROG_DELAY            200         /* tPROG等待延迟,典型值200us,最大需要700us */
#define NAND_TBERS_DELAY            4           /* tBERS等待延迟,典型值3.5ms,最大需要10ms */

/* NAND属性结构体 */
typedef struct
{
    uint16_t page_totalsize;        /* 每页总大小，main区和spare区总和 */
    uint16_t page_mainsize;         /* 每页的main区大小 */
    uint16_t page_sparesize;        /* 每页的spare区大小 */
    uint8_t  block_pagenum;         /* 每个块包含的页数量 */
    uint16_t plane_blocknum;        /* 每个plane包含的块数量 */
    uint16_t block_totalnum;        /* 总的块数量 */
    uint16_t good_blocknum;         /* 好块数量 */
    uint16_t valid_blocknum;        /* 有效块数量(供文件系统使用的好块数量) */
    uint32_t id;                    /* NAND FLAS*(vu8*)(0X80000000|(1<<17))=CMD；*(vu8*)(0X80000000|(1<<17))=CMD；H ID */
    uint16_t *lut;                  /* LUT表，用作逻辑块-物理块转换 */
    uint32_t ecc_hard;              /* 硬件计算出来的ECC值 */
    uint32_t ecc_hdbuf[NAND_MAX_PAGE_SIZE / NAND_ECC_SECTOR_SIZE]; /* ECC硬件计算值缓冲区 */
    uint32_t ecc_rdbuf[NAND_MAX_PAGE_SIZE / NAND_ECC_SECTOR_SIZE]; /* ECC读取的值缓冲区 */
} nand_attriute;

#define NAND_ADDRESS            0X80000000  /* nand flash的访问地址,接NCE3,地址为:0X8000 0000 */
#define NAND_CMD                1 << 16     /* 发送命令 */
#define NAND_ADDR               1 << 17     /* 发送地址 */

/* NAND FLASH命令 */
#define nand_readID             0X90        /* 读ID指令 */
#define NAND_FEATURE            0XEF        /* 设置特性指令 */
#define NAND_RESET              0XFF        /* 复位NAND */
#define NAND_READSTA            0X70        /* 读状态 */
#define NAND_AREA_A             0X00   
#define NAND_AREA_TRUE1         0X30  
#define NAND_WRITE0             0X80
#define NAND_WRITE_TURE1        0X10
#define NAND_ERASE0             0X60
#define NAND_ERASE1             0XD0
#define NAND_MOVEDATA_CMD0      0X00
#define NAND_MOVEDATA_CMD1      0X35
#define NAND_MOVEDATA_CMD2      0X85
#define NAND_MOVEDATA_CMD3      0X10

/* NAND FLASH状态 */
#define NSTA_READY              0X40        /* nand已经准备好 */
#define NSTA_ERROR              0X01        /* nand错误 */
#define NSTA_TIMEOUT            0X02        /* 超时 */
#define NSTA_ECC1BITERR         0X03        /* ECC 1bit错误 */
#define NSTA_ECC2BITERR         0X04        /* ECC 2bit以上错误 */

/* NAND FLASH型号和对应的ID号 */
#define MT29F4G08ABADA          0XDC909556  /* MT29F4G08ABADA */
#define MT29F16G08ABABA         0X48002689  /* MT29F16G08ABABA */


NAND_HandleTypeDef g_nand_handle;     /* NAND FLASH句柄 */
nand_attriute nand_dev;               /* nand重要参数结构体 */

/**
 * @brief       初始化NAND FLASH
 * @param       无
 * @retval      无
 */
uint8_t nand_init(void) {
    FMC_NAND_PCC_TimingTypeDef comspacetiming, attspacetiming;
                                              
    g_nand_handle.Instance = FMC_NAND_DEVICE;
    g_nand_handle.Init.NandBank = FMC_NAND_BANK3;                          /* NAND挂在BANK3上 */
    g_nand_handle.Init.Waitfeature = FMC_NAND_PCC_WAIT_FEATURE_DISABLE;    /* 关闭等待特性 */
    g_nand_handle.Init.MemoryDataWidth = FMC_NAND_PCC_MEM_BUS_WIDTH_8;     /* 8位数据宽度 */
    g_nand_handle.Init.EccComputation = FMC_NAND_ECC_DISABLE;              /* 不使用ECC */
    g_nand_handle.Init.ECCPageSize = FMC_NAND_ECC_PAGE_SIZE_2048BYTE;      /* ECC页大小为2k */
    g_nand_handle.Init.TCLRSetupTime = 0;                                  /* 设置TCLR(tCLR=CLE到RE的延时)=(TCLR+TSET+2)*THCLK,THCLK=1/192M=5.2ns */
    g_nand_handle.Init.TARSetupTime = 1;                                   /* 设置TAR(tAR=ALE到RE的延时)=(TAR+TSET+2)*THCLK,THCLK=1/192M=5.2n */
   
    comspacetiming.SetupTime = 2;           /* 建立时间 */
    comspacetiming.WaitSetupTime = 3;       /* 等待时间 */
    comspacetiming.HoldSetupTime = 2;       /* 保持时间 */
    comspacetiming.HiZSetupTime = 1;        /* 高阻态时间 */
    
    attspacetiming.SetupTime = 2;           /* 建立时间 */
    attspacetiming.WaitSetupTime = 3;       /* 等待时间 */
    attspacetiming.HoldSetupTime = 2;       /* 保持时间 */
    attspacetiming.HiZSetupTime = 1;        /* 高阻态时间 */
    
    HAL_NAND_Init(&g_nand_handle, &comspacetiming, &attspacetiming); 
    nand_reset();                           /* 复位NAND */
    unios_ms_delay(100);
    nand_dev.id = nand_readid();            /* 读取ID */
    nand_modeset(4);                        /* 设置为MODE4,高速模式 */

    if (nand_dev.id == MT29F16G08ABABA) {
        nand_dev.page_totalsize = 4320;     /* nand一个page的总大小（包括spare区） */
        nand_dev.page_mainsize = 4096;      /* nand一个page的有效数据区大小 */
        nand_dev.page_sparesize = 224;      /* nand一个page的spare区大小 */
        nand_dev.block_pagenum = 128;       /* nand一个block所包含的page数目 */
        nand_dev.plane_blocknum = 2048;     /* nand一个plane所包含的block数目 */
        nand_dev.block_totalnum = 4096;     /* nand的总block数目 */
    } else if (nand_dev.id == MT29F4G08ABADA) {
        nand_dev.page_totalsize = 2112;     /* nand一个page的总大小（包括spare区） */
        nand_dev.page_mainsize = 2048;      /* nand一个page的有效数据区大小 */
        nand_dev.page_sparesize = 64;       /* nand一个page的spare区大小 */
        nand_dev.block_pagenum = 64;        /* nand一个block所包含的page数目 */
        nand_dev.plane_blocknum = 2048;     /* nand一个plane所包含的block数目 */
        nand_dev.block_totalnum = 4096;     /* nand的总block数目 */
    } else {
        return 1;                          /* 错误，返回 */
    }

    return 0;
}

/**
 * @brief       NAND FALSH底层驱动,引脚配置，时钟使能
 * @note        此函数会被HAL_nand_init()调用
 * @param       hnand  : nand句柄
 * @retval      无
 */
void HAL_NAND_MspInit(NAND_HandleTypeDef *hnand) {
    GPIO_InitTypeDef gpio_init_struct;

    gpio_init_struct.Pin = NAND_RB_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_INPUT;          /* 输入 */
    gpio_init_struct.Pull = GPIO_PULLUP;              /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_HIGH;         /* 高速 */
    HAL_GPIO_Init(NAND_RB_GPIO_PORT, &gpio_init_struct);

    gpio_init_struct.Pin = GPIO_PIN_9;
    gpio_init_struct.Mode = GPIO_MODE_AF_PP;          /* 输入 */
    gpio_init_struct.Pull = GPIO_NOPULL;              /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_HIGH;         /* 高速 */
    gpio_init_struct.Alternate = GPIO_AF12_FMC;       /* 复用为FMC */
    HAL_GPIO_Init(GPIOG, &gpio_init_struct);  

    /* 初始化PD0,1,4,5,11,12,14,15 */
    gpio_init_struct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5 | \
                           GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_14 | GPIO_PIN_15;
    gpio_init_struct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOD, &gpio_init_struct);

    /* 初始化PE7,8,9,10*/
    gpio_init_struct.Pin = GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10;
    HAL_GPIO_Init(GPIOE, &gpio_init_struct);
}

/**
 * @brief       设置NAND速度模式
 * @param       mode    : 0~5, 表示速度模式
 * @retval      0,成功; 其他,失败
 */
uint8_t nand_modeset(uint8_t mode) {   
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_CMD) = NAND_FEATURE; /* 发送设置特性命令 */
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = 0X01;        /* 地址为0X01,设置mode */
    *(volatile uint8_t *)NAND_ADDRESS = mode;                      /* P1参数,设置mode */
    *(volatile uint8_t *)NAND_ADDRESS = 0;
    *(volatile uint8_t *)NAND_ADDRESS = 0;
    *(volatile uint8_t *)NAND_ADDRESS = 0;
    return (nand_wait_for_ready() != NSTA_READY);
}

/**
 * @brief       读取NAND FLASH的ID
 * @note        不同的NAND略有不同，请根据自己所使用的NAND FALSH数据手册来编写函数
 * @param       无
 * @retval      NAND FLASH的ID值
 */
uint32_t nand_readid(void) {
    uint8_t deviceid[5]; 
    uint32_t id;
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_CMD) = nand_readID; /* 发送读取ID命令 */
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = 0X00;

    /* ID一共有5个字节 */
    deviceid[0] = *(volatile uint8_t *)NAND_ADDRESS;
    deviceid[1] = *(volatile uint8_t *)NAND_ADDRESS;
    deviceid[2] = *(volatile uint8_t *)NAND_ADDRESS;
    deviceid[3] = *(volatile uint8_t *)NAND_ADDRESS;
    deviceid[4] = *(volatile uint8_t *)NAND_ADDRESS;

    /* 镁光的NAND FLASH的ID一共5个字节，但是为了方便我们只取4个字节组成一个32位的ID值
       根据NAND FLASH的数据手册，只要是镁光的NAND FLASH，那么一个字节ID的第一个字节都是0X2C
       所以我们就可以抛弃这个0X2C，只取后面四字节的ID值。*/
    id = ((uint32_t)deviceid[1]) << 24 | ((uint32_t)deviceid[2]) << 16 | ((uint32_t)deviceid[3]) << 8 | deviceid[4];

    return id;
}

/**
 * @brief       读NAND状态
 * @param       无
 * @retval      NAND状态值
 *              bit0:0,成功; 1,错误(编程/擦除/READ)
                bit6:0,Busy; 1,Ready
 */
uint8_t nand_readstatus(void) {
    volatile uint8_t data = 0;

    *(volatile uint8_t *)(NAND_ADDRESS | NAND_CMD) = NAND_READSTA;  /* 发送读状态命令 */
    nand_delay(NAND_TWHR_DELAY);                                    /* 等待tWHR,再读取状态寄存器 */
    data = *(volatile uint8_t *)NAND_ADDRESS;                       /* 读取状态值 */

    return data;
}

/**
 * @brief       等待NAND准备好
 * @param       无
 * @retval      NSTA_TIMEOUT  等待超时了
 *              NSTA_READY    已经准备好
 */
uint8_t nand_wait_for_ready(void) {
    uint8_t status = 0;
    volatile uint32_t time = 0;

    while(1) {
        /* 等待ready */
        status = nand_readstatus(); /* 获取状态值 */
        if (status & NSTA_READY) break;
        time++;
        if (time >= 0X1FFFFFFF) return NSTA_TIMEOUT;    /* 超时 */
    }

    return NSTA_READY;              /* 准备好 */
}

/**
 * @brief       复位NAND
 * @param       无
 * @retval      0,成功; 其他,失败
 */
uint8_t nand_reset(void) {
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_CMD) = NAND_RESET; /* 复位NAND */
    if (nand_wait_for_ready() == NSTA_READY) {
        return 0;                   /* 复位成功 */
    }
    else return 1;                  /* 复位失败 */
}

/**
 * @brief       等待RB信号为某个电平
 * @param       rb      : 0,等待RB==0;
 *                        1,等待RB==1;
 * @retval      0,成功; 1,超时
 */
uint8_t nand_waitrb(volatile uint8_t rb) {
    volatile uint32_t time = 0;

    while (time < 0X1FFFFFF) {
        time++; 
        if (NAND_RB == rb) return 0;
    }

    return 1;
}

/**
 * @brief       NAND延时
 * @note        一个i++至少需要4ns
 * @param       i       : 等待的时间
 * @retval      无
 */
void nand_delay(volatile uint32_t i) {
    while (i > 0) i--;
}

/**
 * @brief       读取NAND Flash的指定页指定列的数据(main区和spare区都可以使用此函数)
 * @param       pagenum         : 要读取的页地址,范围:0~(block_pagenum*block_totalnum-1)
 * @param       colnum          : 要读取的列开始地址(也就是页内地址),范围:0~(page_totalsize-1)
 * @param       *pbuffer        : 指向数据存储区
 * @param       numbyte_to_read : 读取字节数(不能跨页读)
 * @retval      0,成功; 其他,错误代码
 */
uint8_t nand_readpage(uint32_t pagenum, uint16_t colnum, uint8_t *pbuffer, uint16_t numbyte_to_read) {
    volatile uint16_t i = 0;
    uint8_t res = 0;
    uint8_t eccnum = 0;     /* 需要计算的ECC个数，每NAND_ECC_SECTOR_SIZE字节计算一个ecc */
    uint8_t eccstart = 0;   /* 第一个ECC值所属的地址范围 */
    uint8_t errsta = 0;
    uint8_t *p;

    *(volatile uint8_t *)(NAND_ADDRESS|NAND_CMD) = NAND_AREA_A;
    /* 发送地址 */
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)colnum;
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)(colnum >> 8);
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)pagenum;
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)(pagenum >> 8);
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)(pagenum >> 16);
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_CMD) = NAND_AREA_TRUE1;

    /*下面两行代码是等待R/B引脚变为低电平，其实主要起延时作用的，等待NAND操作R/B引脚。因为我们是通过
       将STM32的NWAIT引脚(NAND的R/B引脚)配置为普通IO，代码中通过读取NWAIT引脚的电平来判断NAND是否准备
       就绪的。这个也就是模拟的方法，所以在速度很快的时候有可能NAND还没来得及操作R/B引脚来表示NAND的忙
       闲状态，结果我们就读取了R/B引脚,这个时候肯定会出错的，事实上确实是会出错!大家也可以将下面两行
       代码换成延时函数,只不过这里我们为了效率所以没有用延时函数。 */
    res = nand_waitrb(0);                                                               /* 等待RB=0 */
    if (res) return NSTA_TIMEOUT;                                                       /* 超时退出 */

    /* 下面2行代码是真正判断NAND是否准备好的 */
    res = nand_waitrb(1);                                                               /* 等待RB=1 */
    if (res) return NSTA_TIMEOUT;                                                       /* 超时退出 */

    if (numbyte_to_read % NAND_ECC_SECTOR_SIZE)                                         /* 不是NAND_ECC_SECTOR_SIZE的整数倍，不进行ECC校验 */
    { 
        /* 读取NAND FLASH中的值 */
        for (i = 0;i < numbyte_to_read; i++)
        {
            *(volatile uint8_t *)pbuffer++ = *(volatile uint8_t *)NAND_ADDRESS;
        }
    }
    else
    {
        eccnum = numbyte_to_read / NAND_ECC_SECTOR_SIZE;                                /* 得到ecc计算次数 */
        eccstart = colnum / NAND_ECC_SECTOR_SIZE;
        p = pbuffer;

        for (res = 0; res < eccnum; res++)
        {
            FMC_Bank2_3->PCR3 |= 1 << 6;                                                /* 使能ECC校验 */

            for (i = 0; i < NAND_ECC_SECTOR_SIZE; i++)                                  /* 读取NAND_ECC_SECTOR_SIZE个数据 */
            {
                *(volatile uint8_t *)pbuffer++ = *(volatile uint8_t *)NAND_ADDRESS;
            }

            while (!(FMC_Bank2_3 -> SR3 & (1 << 6)));                                   /* 等待FIFO空 */

            nand_dev.ecc_hdbuf[res + eccstart] = FMC_Bank2_3 -> ECCR3;                  /* 读取硬件计算后的ECC值 */
            FMC_Bank2_3 -> PCR3 &= ~(1 << 6);                                           /* 禁止ECC校验 */
        } 

        i = nand_dev.page_mainsize + 0X10 + eccstart * 4;                               /* 从spare区的0X10位置开始读取之前存储的ecc值 */
        nand_delay(NAND_TRHW_DELAY);                                                    /* 等待tRHW */
        *(volatile uint8_t *)(NAND_ADDRESS | NAND_CMD) = 0X05;                          /* 随机读指令 */
        /* 发送地址 */
        *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)i;
        *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)(i >> 8);
        *(volatile uint8_t *)(NAND_ADDRESS | NAND_CMD) = 0XE0;                          /* 开始读数据 */

        nand_delay(NAND_TWHR_DELAY);                                                    /* 等待tWHR */
        pbuffer = (uint8_t *)&nand_dev.ecc_rdbuf[eccstart];
  
        for (i = 0; i < 4 * eccnum; i++)                                                /* 读取保存的ECC值 */
        {
            *(volatile uint8_t *)pbuffer++ = *(volatile uint8_t *)NAND_ADDRESS;
        }
  
        for (i = 0; i < eccnum; i++)                                                    /* 检验ECC */
        {
            if (nand_dev.ecc_rdbuf[i + eccstart] != nand_dev.ecc_hdbuf[i + eccstart])   /* 不相等,需要校正 */
            {
                logd("err hd,rd:0x%x,0x%x\r\n", nand_dev.ecc_hdbuf[i + eccstart], nand_dev.ecc_rdbuf[i + eccstart]); 
                logd("eccnum,eccstart:%d,%d\r\n", eccnum, eccstart);
                logd("PageNum,ColNum:%d,%d\r\n", pagenum, colnum);
                res = nand_ecc_correction(p + NAND_ECC_SECTOR_SIZE * i, nand_dev.ecc_rdbuf[i + eccstart], nand_dev.ecc_hdbuf[i + eccstart]);/* ECC校验 */

                if (res)
                {
                    errsta = NSTA_ECC2BITERR;                                           /* 标记2BIT及以上ECC错误 */
                }
                else
                {
                    errsta = NSTA_ECC1BITERR;                                           /* 标记1BIT ECC错误 */
                }
            }
        }
    }

    if (nand_wait_for_ready() != NSTA_READY)
    {
        errsta = NSTA_ERROR;    /* 失败 */
    }

    return errsta;              /* 成功 */
}

/**
 * @brief       读取NAND Flash的指定页指定列的数据(main区和spare区都可以使用此函数),并对比(FTL管理时需要)
 * @param       pagenum         : 要读取的页地址,范围:0~(block_pagenum*block_totalnum-1)
 * @param       colnum          : 要读取的列开始地址(也就是页内地址),范围:0~(page_totalsize-1)
 * @param       cmpval          : 要对比的值,以uint32_t为单位
 * @param       numbyte_to_read : 读取字数(以4字节为单位,不能跨页读)
 * @param       numbyte_equal   : 从初始位置持续与CmpVal值相同的数据个数
 * @retval      0,成功; 其他,错误代码
 */
uint8_t nand_readpagecomp(uint32_t pagenum, uint16_t colnum, uint32_t cmpval, uint16_t numbyte_to_read, uint16_t *numbyte_equal)
{
    uint16_t i = 0;
    uint8_t res = 0;
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_CMD) = NAND_AREA_A;
    /* 发送地址 */
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)colnum;
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)(colnum >> 8);
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)pagenum;
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)(pagenum >> 8);
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)(pagenum >> 16);
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_CMD) = NAND_AREA_TRUE1;

    /**
     * 下面两行代码是等待R/B引脚变为低电平，其实主要起延时作用的，等待NAND操作R/B引脚。因为我们是通过
     * 将STM32的NWAIT引脚(NAND的R/B引脚)配置为普通IO，代码中通过读取NWAIT引脚的电平来判断NAND是否准备
     * 就绪的。这个也就是模拟的方法，所以在速度很快的时候有可能NAND还没来得及操作R/B引脚来表示NAND的忙
     * 闲状态，结果我们就读取了R/B引脚,这个时候肯定会出错的，事实上确实是会出错!大家也可以将下面两行
     * 代码换成延时函数,只不过这里我们为了效率所以没有用延时函数。
     */
    res = nand_waitrb(0);                                   /* 等待RB=0 */

    if (res)
    {
        return NSTA_TIMEOUT;                                /* 超时退出 */
    }

    /* 下面2行代码是真正判断NAND是否准备好的 */
    res = nand_waitrb(1);                                   /* 等待RB=1 */

    if (res)
    {
        return NSTA_TIMEOUT;                                /* 超时退出 */
    }

    for (i = 0; i < numbyte_to_read; i++)                   /* 读取数据,每次读4字节 */
    {
        if (*(volatile uint32_t *)NAND_ADDRESS != cmpval)break;  /* 如果有任何一个值,与cmpval不相等,则退出. */
    }

    *numbyte_equal = i;                                     /* 与cmpval值相同的个数 */

    if (nand_wait_for_ready() != NSTA_READY)
    {
        return NSTA_ERROR;                                  /* 失败 */
    }

    return 0;                                               /* 成功 */
}

/**
 * @brief       在NAND一页中写入指定个字节的数据(main区和spare区都可以使用此函数)
 * @param       pagenum         : 要写入的页地址,范围:0~(block_pagenum*block_totalnum-1)
 * @param       colnum          : 要写入的列开始地址(也就是页内地址),范围:0~(page_totalsize-1)
 * @param       pbuffer         : 指向数据存储区
 * @param       numbyte_to_write: 要写入的字节数，该值不能超过该页剩余字节数！！！
 * @retval      0,成功; 其他,错误代码
 */
uint8_t nand_writepage(uint32_t pagenum, uint16_t colnum, uint8_t *pbuffer, uint16_t numbyte_to_write)
{
    volatile uint16_t i = 0;  
    uint8_t res = 0;
    uint8_t eccnum = 0;                                                 /* 需要计算的ECC个数，每NAND_ECC_SECTOR_SIZE字节计算一个ecc */
    uint8_t eccstart = 0;                                               /* 第一个ECC值所属的地址范围 */

    *(volatile uint8_t *)(NAND_ADDRESS | NAND_CMD) = NAND_WRITE0;
    /* 发送地址 */
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)colnum;
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)(colnum >> 8);
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)pagenum;
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)(pagenum >> 8);
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)(pagenum >> 16);
    nand_delay(NAND_TADL_DELAY);                                        /* 等待tADL */

    if (numbyte_to_write % NAND_ECC_SECTOR_SIZE)                        /* 不是NAND_ECC_SECTOR_SIZE的整数倍，不进行ECC校验 */
    {  
        for (i = 0; i < numbyte_to_write; i++)                          /* 写入数据 */
        {
            *(volatile uint8_t *)NAND_ADDRESS = *(volatile uint8_t *)pbuffer++;
        }
    }
    else
    {
        eccnum = numbyte_to_write / NAND_ECC_SECTOR_SIZE;               /* 得到ecc计算次数 */
        eccstart = colnum / NAND_ECC_SECTOR_SIZE;

        for (res = 0; res < eccnum; res++)
        {
            FMC_Bank2_3 -> PCR3 |= 1 << 6;                              /* 使能ECC校验 */

            for (i = 0; i < NAND_ECC_SECTOR_SIZE; i++)                  /* 写入NAND_ECC_SECTOR_SIZE个数据 */
            {
                *(volatile uint8_t *)NAND_ADDRESS = *(volatile uint8_t *)pbuffer++;
            }
            while (!(FMC_Bank2_3 -> SR3 & (1 << 6)));                   /* 等待FIFO空 */

            nand_dev.ecc_hdbuf[res+eccstart] = FMC_Bank2_3 -> ECCR3;    /* 读取硬件计算后的ECC值 */

            FMC_Bank2_3 -> PCR3 &= ~(1 << 6);                           /* 禁止ECC校验 */
        }

        i = nand_dev.page_mainsize + 0X10 + eccstart * 4;               /*计算写入ECC的spare区地址 */
        nand_delay(NAND_TADL_DELAY);                                    /* 等待tADL */
        *(volatile uint8_t *)(NAND_ADDRESS | NAND_CMD) = 0X85;          /* 随机写指令 */

        /* 发送地址 */
        *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)i;
        *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)(i >> 8);
        nand_delay(NAND_TADL_DELAY);                                    /* 等待tADL */
        pbuffer = (uint8_t *)&nand_dev.ecc_hdbuf[eccstart];

        for (i = 0; i < eccnum; i++)                                    /* 写入ECC */
        { 
            for (res = 0; res < 4; res++)
            {
                *(volatile uint8_t *)NAND_ADDRESS = *(volatile uint8_t *)pbuffer++;
            }
        }
    }
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_CMD) = NAND_WRITE_TURE1; 
    unios_us_delay(NAND_TPROG_DELAY);                                         /* 等待tPROG */

    if (nand_wait_for_ready() != NSTA_READY)
    {
        return NSTA_ERROR;                                              /* 失败 */
    }

    return 0;                                                           /* 成功 */
}

/**
 * @brief       在NAND一页中的指定地址开始,写入指定长度的恒定数字
 * @param       pagenum         : 要写入的页地址,范围:0~(block_pagenum*block_totalnum-1)
 * @param       colnum          : 要写入的列开始地址(也就是页内地址),范围:0~(page_totalsize-1)
 * @param       cval            : 要写入的指定常数
 * @param       numbyte_to_write: 要写入的字节数(以4字节为单位)
 * @retval      0,成功; 其他,错误代码
 */
uint8_t nand_write_pageconst(uint32_t pagenum, uint16_t colnum, uint32_t cval, uint16_t numbyte_to_write)
{
    uint16_t i = 0;  
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_CMD) = NAND_WRITE0;
    /* 发送地址 */
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)colnum;
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)(colnum >> 8);
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)pagenum;
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)(pagenum >> 8);
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)(pagenum >> 16);

    nand_delay(NAND_TADL_DELAY);                /* 等待tADL */

    for (i = 0; i < numbyte_to_write; i++)      /* 写入数据,每次写4字节 */
    {
        *(volatile uint32_t *)NAND_ADDRESS = cval;
    }

    *(volatile uint8_t *)(NAND_ADDRESS | NAND_CMD) = NAND_WRITE_TURE1; 
    unios_us_delay(NAND_TPROG_DELAY);                 /* 等待tPROG */

    if (nand_wait_for_ready() != NSTA_READY)
    {
        return NSTA_ERROR;                      /* 失败  */
    }

    return 0;                                   /* 成功 */
}

/**
 * @brief       将一页数据拷贝到另一页,不写入新数据
 * @note        源页和目的页要在同一个Plane内！
 * @param       source_pagenum  : 源页地址,范围:0~(block_pagenum*block_totalnum-1)
 * @param       dest_pagenum    : 目的页地址,范围:0~(block_pagenum*block_totalnum-1)
 * @retval      0,成功; 其他,错误代码
 */
uint8_t nand_copypage_withoutwrite(uint32_t source_pagenum, uint32_t dest_pagenum)
{
    uint8_t res = 0;
    uint16_t source_block = 0, dest_block = 0;
    /* 判断源页和目的页是否在同一个plane中 */
    source_block = source_pagenum / nand_dev.block_pagenum;
    dest_block = dest_pagenum / nand_dev.block_pagenum;

    if ((source_block % 2) != (dest_block % 2))
    {
        return NSTA_ERROR;  /* 不在同一个plane内 */
    }

    *(volatile uint8_t *)(NAND_ADDRESS | NAND_CMD) = NAND_MOVEDATA_CMD0; /* 发送命令0X00 */
    /* 发送源页地址 */
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)0;
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)0;
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)source_pagenum;
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)(source_pagenum >> 8);
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)(source_pagenum >> 16);
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_CMD) = NAND_MOVEDATA_CMD1;/* 发送命令0X35 */
    /**
     *   下面两行代码是等待R/B引脚变为低电平，其实主要起延时作用的，等待NAND操作R/B引脚。因为我们是通过
     *   将STM32的NWAIT引脚(NAND的R/B引脚)配置为普通IO，代码中通过读取NWAIT引脚的电平来判断NAND是否准备
     *   就绪的。这个也就是模拟的方法，所以在速度很快的时候有可能NAND还没来得及操作R/B引脚来表示NAND的忙
     *   闲状态，结果我们就读取了R/B引脚,这个时候肯定会出错的，事实上确实是会出错!大家也可以将下面两行
     *   代码换成延时函数,只不过这里我们为了效率所以没有用延时函数。
    */
    res = nand_waitrb(0);        /* 等待RB=0 */

    if (res)
    {
        return NSTA_TIMEOUT;     /* 超时退出 */
    }
    /* 下面2行代码是真正判断NAND是否准备好的 */
    res = nand_waitrb(1);        /* 等待RB=1 */

    if (res)
    {
        return NSTA_TIMEOUT;    /* 超时退出 */
    }

    *(volatile uint8_t *)(NAND_ADDRESS | NAND_CMD) = NAND_MOVEDATA_CMD2;  /* 发送命令0X85 */
    /* 发送目的页地址 */
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)0;
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)0;
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)dest_pagenum;
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)(dest_pagenum >> 8);
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)(dest_pagenum >> 16);
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_CMD) = NAND_MOVEDATA_CMD3;    /* 发送命令0X10 */
    unios_us_delay(NAND_TPROG_DELAY);                                             /* 等待tPROG  */

    if (nand_wait_for_ready() != NSTA_READY)
    {
        return NSTA_ERROR;                                                  /* NAND未准备好 */
    }

    return 0;                                                               /* 成功 */
}

/**
 * @brief       将一页数据拷贝到另一页,并且可以写入数据
 * @note        源页和目的页要在同一个Plane内！
 * @param       source_pagenum  : 源页地址,范围:0~(block_pagenum*block_totalnum-1)
 * @param       dest_pagenum    : 目的页地址,范围:0~(block_pagenum*block_totalnum-1)
 * @param       colnum          : 页内列地址,范围:0~(page_totalsize-1)
 * @param       pbuffer         : 要写入的数据
 * @param       numbyte_to_write: 要写入的数据个数
 * @retval      0,成功; 其他,错误代码
 */
uint8_t nand_copypage_withwrite(uint32_t source_pagenum, uint32_t dest_pagenum, uint16_t colnum, uint8_t *pbuffer, uint16_t numbyte_to_write)
{
    uint8_t res = 0;
    volatile uint16_t i = 0;
    uint16_t source_block = 0, dest_block = 0;  
    uint8_t eccnum = 0;                                             /* 需要计算的ECC个数，每NAND_ECC_SECTOR_SIZE字节计算一个ecc */
    uint8_t eccstart = 0;                                           /* 第一个ECC值所属的地址范围 */
    /* 判断源页和目的页是否在同一个plane中 */
    source_block = source_pagenum / nand_dev.block_pagenum;
    dest_block = dest_pagenum / nand_dev.block_pagenum;

    if ((source_block % 2) != (dest_block % 2))
    {
        return NSTA_ERROR;                                           /* 不在同一个plane内 */
    }

    *(volatile uint8_t *)(NAND_ADDRESS | NAND_CMD) = NAND_MOVEDATA_CMD0;  /* 发送命令0X00 */
    /* 发送源页地址 */
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)0;
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)0;
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)source_pagenum;
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)(source_pagenum >> 8);
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)(source_pagenum >> 16);
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_CMD) = NAND_MOVEDATA_CMD1;  /* 发送命令0X35 */

    /**
     * 下面两行代码是等待R/B引脚变为低电平，其实主要起延时作用的，等待NAND操作R/B引脚。因为我们是通过
     * 将STM32的NWAIT引脚(NAND的R/B引脚)配置为普通IO，代码中通过读取NWAIT引脚的电平来判断NAND是否准备
     * 就绪的。这个也就是模拟的方法，所以在速度很快的时候有可能NAND还没来得及操作R/B引脚来表示NAND的忙
     * 闲状态，结果我们就读取了R/B引脚,这个时候肯定会出错的，事实上确实是会出错!大家也可以将下面两行
     * 代码换成延时函数,只不过这里我们为了效率所以没有用延时函数。
     */
    res = nand_waitrb(0);                                              /* 等待RB=0 */

    if (res)
    {
        return NSTA_TIMEOUT;                                           /* 超时退出 */
    }

    /* 下面2行代码是真正判断NAND是否准备好的 */
    res = nand_waitrb(1);                                              /* 等待RB=1 */

    if (res)
    {
        return NSTA_TIMEOUT;                                           /* 超时退出 */
    }
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_CMD) = NAND_MOVEDATA_CMD2;    /* 发送命令0X85 */
    /* 发送目的页地址 */
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)colnum;
    *(volatile uint8_t *)(NAND_ADDRESS  |NAND_ADDR) = (uint8_t)(colnum >> 8);
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)dest_pagenum;
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)(dest_pagenum >> 8);
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)(dest_pagenum >> 16); 
    /* 发送页内列地址 */
    nand_delay(NAND_TADL_DELAY);                                        /* 等待tADL */

    if (numbyte_to_write % NAND_ECC_SECTOR_SIZE)                        /* 不是NAND_ECC_SECTOR_SIZE的整数倍，不进行ECC校验 */
    {  
        for (i = 0; i < numbyte_to_write; i++)                          /* 写入数据 */
        {
            *(volatile uint8_t *)NAND_ADDRESS = *(volatile uint8_t *)pbuffer++;
        }
    }
    else
    {
        eccnum = numbyte_to_write / NAND_ECC_SECTOR_SIZE;               /* 得到ecc计算次数 */
        eccstart = colnum / NAND_ECC_SECTOR_SIZE;

        for (res = 0; res < eccnum; res++)
        {
            FMC_Bank2_3 -> PCR3 |= 1 << 6;                              /* 使能ECC校验 */

            for (i = 0; i < NAND_ECC_SECTOR_SIZE; i++)                  /* 写入NAND_ECC_SECTOR_SIZE个数据 */
            {
                *(volatile uint8_t *)NAND_ADDRESS = *(volatile uint8_t *)pbuffer++;
            }

            while (!(FMC_Bank2_3 -> SR3 & (1<<6)));                     /* 等待FIFO空 */

            nand_dev.ecc_hdbuf[res + eccstart] = FMC_Bank2_3 -> ECCR3;  /* 读取硬件计算后的ECC值 */

            FMC_Bank2_3 -> PCR3 &= ~(1 << 6);                           /* 禁止ECC校验 */
        }

        i = nand_dev.page_mainsize + 0X10 + eccstart * 4;               /* 计算写入ECC的spare区地址 */
        nand_delay(NAND_TADL_DELAY);                                    /* 等待tADL */
        *(volatile uint8_t *)(NAND_ADDRESS | NAND_CMD) = 0X85;          /* 随机写指令 */
        /* 发送地址 */
        *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)i;
        *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)(i >> 8);
        nand_delay(NAND_TADL_DELAY);                                    /* 等待tADL */

        pbuffer = (uint8_t *)&nand_dev.ecc_hdbuf[eccstart];

        for (i = 0; i < eccnum; i++)                                    /* 写入ECC */
        { 
            for (res = 0; res < 4; res++)
            {
                *(volatile uint8_t *)NAND_ADDRESS = *(volatile uint8_t *)pbuffer++;
            }
        }
    }

    *(volatile uint8_t *)(NAND_ADDRESS | NAND_CMD) = NAND_MOVEDATA_CMD3;     /* 发送命令0X10 */
    unios_us_delay(NAND_TPROG_DELAY);                                         /* 等待tPROG */

    if (nand_wait_for_ready() != NSTA_READY)
    {
        return NSTA_ERROR;                                              /* 失败 */
    }

    return 0;                                                           /* 成功 */ 
}

/**
 * @brief       读取spare区中的数据
 * @param       pagenum         : 要写入的页地址,范围:0~(block_pagenum*block_totalnum-1)
 * @param       colnum          : 要写入的spare区地址(spare区中哪个地址),范围:0~(page_sparesize-1)
 * @param       pbuffer         : 接收数据缓冲区
 * @param       numbyte_to_read : 要读取的字节数(不大于page_sparesize)
 * @retval      0,成功; 其他,错误代码
 */
uint8_t nand_readspare(uint32_t pagenum, uint16_t colnum, uint8_t *pbuffer, uint16_t numbyte_to_read)
{
    uint8_t temp = 0;
    uint8_t remainbyte = 0;
    remainbyte = nand_dev.page_sparesize - colnum;

    if (numbyte_to_read > remainbyte) 
    {
        numbyte_to_read = remainbyte;           /* 确保要写入的字节数不大于spare剩余的大小 */
    }

    temp = nand_readpage(pagenum, colnum + nand_dev.page_mainsize, pbuffer, numbyte_to_read);   /* 读取数据 */
    return temp;
}

/**
 * @brief       向spare区中写数据
 * @param       pagenum         : 要写入的页地址,范围:0~(block_pagenum*block_totalnum-1)
 * @param       colnum          : 要写入的spare区地址(spare区中哪个地址),范围:0~(page_sparesize-1)
 * @param       pbuffer         : 要写入的数据首地址
 * @param       numbyte_to_write: 要写入的字节数(不大于page_sparesize)
 * @retval      0,成功; 其他,失败
 */
uint8_t nand_writespare(uint32_t pagenum, uint16_t colnum, uint8_t *pbuffer, uint16_t numbyte_to_write)
{
    uint8_t temp = 0;
    uint8_t remainbyte = 0;

    remainbyte = nand_dev.page_sparesize - colnum;

    if (numbyte_to_write > remainbyte)
    {
        numbyte_to_write = remainbyte;          /* 确保要读取的字节数不大于spare剩余的大小 */
    }

    temp = nand_writepage(pagenum, colnum + nand_dev.page_mainsize, pbuffer, numbyte_to_write); /* 读取 */

    return temp;
}

/**
 * @brief       擦除一个块
 * @param       blocknum        : 要擦除的BLOCK编号,范围:0-(block_totalnum-1)
 * @retval      0,擦除成功; 其他,擦除失败
 */
uint8_t nand_eraseblock(uint32_t blocknum)
{
    if (nand_dev.id == MT29F16G08ABABA)
    {
        blocknum <<= 7;             /* 将块地址转换为页地址 */
    }
    else if (nand_dev.id == MT29F4G08ABADA)
    {
        blocknum <<= 6;
    }

    *(volatile uint8_t *)(NAND_ADDRESS | NAND_CMD) = NAND_ERASE0;
    /* 发送块地址 */
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)blocknum;
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)(blocknum >> 8);
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_ADDR) = (uint8_t)(blocknum >> 16);
    *(volatile uint8_t *)(NAND_ADDRESS | NAND_CMD) = NAND_ERASE1;

    unios_ms_delay(NAND_TBERS_DELAY);     /* 等待擦除成功 */

    if (nand_wait_for_ready() != NSTA_READY)
    {
        return NSTA_ERROR;          /* 失败 */
    }

    return 0;                       /* 成功 */
}

/**
 * @brief       全片擦除NAND FLASH
 * @param       无
 * @retval      无
 */
void nand_erasechip(void)
{
    uint8_t status;
    uint16_t i = 0;

    for (i = 0; i < nand_dev.block_totalnum; i++)                     /* 循环擦除所有的块 */
    {
        status = nand_eraseblock(i);

        if (status)
        {
            logd("Erase %d block fail: %d\r\n", i, status);  /* 擦除失败 */
        }
    }
}

/**
 * @brief       获取ECC的奇数位/偶数位
 * @param       oe      : 0,偶数位; 1,奇数位
 * @param       eccval  : 输入的ecc值
 * @retval      计算后的ecc值(最多16位)
 */
uint16_t nand_ecc_get_oe(uint8_t oe, uint32_t eccval)
{
    uint8_t i;
    uint16_t ecctemp = 0;

    for (i = 0; i < 24; i++)
    {
        if ((i % 2) == oe)
        {
            if ((eccval >> i) & 0X01)
            {
                ecctemp += 1 << (i >> 1); 
            }
        }
    }

    return ecctemp;
}

/**
 * @brief       ECC校正函数
 * @param       data_buf    : 数据缓存区
 * @param       eccrd       : 读取出来, 原来保存的ECC值
 * @param       ecccl       : 读取数据时, 硬件计算的ECC值
 * @retval      0,错误已修正; 其他,ECC错误(有大于2个bit的错误,无法恢复)
 */
uint8_t nand_ecc_correction(uint8_t* data_buf, uint32_t eccrd, uint32_t ecccl)
{
    uint16_t eccrdo, eccrde, eccclo, ecccle;
    uint16_t eccchk = 0;
    uint16_t errorpos = 0;
    uint32_t bytepos = 0;
    eccrdo = nand_ecc_get_oe(1, eccrd);     /* 获取eccrd的奇数位 */
    eccrde = nand_ecc_get_oe(0, eccrd);     /* 获取eccrd的偶数位 */
    eccclo = nand_ecc_get_oe(1, ecccl);     /* 获取ecccl的奇数位 */
    ecccle = nand_ecc_get_oe(0, ecccl);     /* 获取ecccl的偶数位 */
    eccchk = eccrdo^eccrde^eccclo^ecccle;

    if (eccchk == 0XFFF)                    /* 全1,说明只有1bit ECC错误 */
    {
        errorpos = eccrdo^eccclo; 
        logd("errorpos:%d\r\n", errorpos); 
        bytepos = errorpos / 8; 
        data_buf[bytepos]^=1 << (errorpos % 8);
    }
    else                                    /* 不是全1,说明至少有2bit ECC错误,无法修复 */
    {
        logd("2bit ecc error or more\r\n");
        return 1;
    } 

    return 0;
}





/**
 *        每个块,第一个page的spare区,前四个字节的含义:
 *        第一个字节,表示该块是否是坏块:0XFF,正常块;其他值,坏块.
 *        第二个字节,表示该块是否被用过:0XFF,没有写过数据;0XCC,写过数据了.
 *        第三和第四个字节,表示该块所属的逻辑块编号. 

 *        每个page,spare区16字节以后的字节含义:
 *        第十六字节开始,后续每4个字节用于存储一个扇区(大小:NAND_ECC_SECTOR_SIZE)的ECC值,用于ECC校验
 */

/**
 * @brief       FTL层初始化
 * @param       无
 * @retval      0,正常；其他,失败
 */
uint8_t ftl_init(void)
{
    uint8_t temp;

    if (nand_init())
    {
        return 1;                                                   /* 初始化NAND FLASH */
    }

    if (nand_dev.lut)
    {
        unios_free(nand_dev.lut);
    }

    nand_dev.lut = unios_malloc((nand_dev.block_totalnum) * 2); /* 给LUT表申请内存 */
    memset(nand_dev.lut, 0, nand_dev.block_totalnum * 2);           /* 全部清理 */

    if (!nand_dev.lut)
    {
        return 1;                                                   /* 内存申请失败  */
    }

    temp = ftl_create_lut(1);

    if (temp) 
    {
        logd("format nand flash...\r\n");
        temp = ftl_format();                                        /* 格式化NAND */

        if (temp)
        {
            logd("format failed!\r\n");
            return 2;
        }
    }
    else                                                            /* 创建LUT表成功 */
    {
        logd("total block num: %d\r\n", nand_dev.block_totalnum);
        logd("good block num: %d\r\n", nand_dev.good_blocknum);
        logd("valid block num: %d\r\n", nand_dev.valid_blocknum);
    }

    return 0;
} 

/**
 * @brief       标记某一个块为坏块
 * @param       blocknum   : 块编号,范围:0~(block_totalnum-1)
 * @retval      无
 */
void ftl_badblock_mark(uint32_t blocknum)
{
    uint32_t temp = 0XAAAAAAAA; /* 坏块标记mark,任意值都OK,只要不是0XFF.这里写前4个字节,方便ftl_find_unused_block函数检查坏块.(不检查备份区,以提高速度) */
    nand_writespare(blocknum * nand_dev.block_pagenum, 0, (uint8_t *)&temp, 4);      /* 在第一个page的spare区,第一个字节做坏块标记(前4个字节都写) */
    nand_writespare(blocknum * nand_dev.block_pagenum + 1, 0, (uint8_t *)&temp, 4);  /* 在第二个page的spare区,第一个字节做坏块标记(备份用,前4个字节都写) */
}

/**
 * @brief       检查某一块是否是坏块
 * @param       blocknum    : 块编号,范围:0~(block_totalnum-1)
 * @retval      0,好块;
 *              其他,坏块
 */
uint8_t ftl_check_badblock(uint32_t blocknum)
{
    uint8_t flag = 0;

    nand_readspare(blocknum * nand_dev.block_pagenum, 0, &flag, 1);         /* 读取坏块标志 */

    if (flag == 0XFF)                                                       /* 好块?,读取备份区坏块标记 */
    {
        nand_readspare(blocknum * nand_dev.block_pagenum + 1, 0, &flag, 1); /* 读取备份区坏块标志 */

        if (flag == 0XFF)
        {
            return 0;                                                       /* 好块 */
        }

        else return 1;                                                      /* 坏块 */
    }

    return 2; 
}

/**
 * @brief       标记某一个块已经使用
 * @param       blocknum    : 块编号,范围:0~(block_totalnum-1)
 * @retval      0,成功;
 *              其他,失败
 */
uint8_t ftl_used_blockmark(uint32_t blocknum)
{
    uint8_t usedflag = 0XCC;
    uint8_t temp = 0;
    temp = nand_writespare(blocknum * nand_dev.block_pagenum, 1, (uint8_t *)&usedflag, 1); /* 写入块已经被使用标志 */

    return temp;
}

/**
 * @brief       从给定的块开始找到往前找到一个未被使用的块(指定奇数/偶数)
 * @param       sblock      : 开始块,范围:0~(block_totalnum-1)
 * @param       flag        : 0,偶数快; 1,奇数块.
 * @retval      0XFFFFFFFF,失败;
 *              其他值,未使用块号
 */
uint32_t ftl_find_unused_block(uint32_t sblock, uint8_t flag)
{
    uint32_t temp = 0;
    uint32_t blocknum = 0;

    for (blocknum = sblock + 1; blocknum > 0; blocknum--)
    {
        if (((blocknum - 1) % 2) == flag)   /* 奇偶合格,才检测 */
        {
            nand_readspare((blocknum - 1) * nand_dev.block_pagenum, 0, (uint8_t *)&temp, 4); /* 读块是否被使用标记 */
            if (temp == 0XFFFFFFFF)
            {
                return (blocknum - 1);      /* 找到一个空块,返回块编号 */
            }
        }
    }

    return 0XFFFFFFFF;                      /* 未找到空余块 */
}

/**
 * @brief       查找与给定块在同一个plane内的未使用的块
 * @param       sblock    ： 给定块,范围:0~(block_totalnum-1)
 * @retval      0XFFFFFFFF,失败;其他值,未使用块号
 */
uint32_t ftl_find_same_plane_unused_block(uint32_t sblock)
{
    static uint32_t curblock = 0XFFFFFFFF;
    uint32_t unusedblock = 0;

    if (curblock > (nand_dev.block_totalnum - 1))
    {
        curblock = nand_dev.block_totalnum - 1;                   /* 超出范围了,强制从最后一个块开始 */
    }

    unusedblock = ftl_find_unused_block(curblock,sblock % 2);     /* 从当前块,开始,向前查找空余块  */

    if (unusedblock == 0XFFFFFFFF && curblock < (nand_dev.block_totalnum - 1))  /* 未找到,且不是从最末尾开始找的 */
    {
        curblock = nand_dev.block_totalnum - 1;                   /* 强制从最后一个块开始 */
        unusedblock = ftl_find_unused_block(curblock,sblock % 2); /* 从最末尾开始,重新找一遍 */ 
    }
    if (unusedblock == 0XFFFFFFFF)
    {
        return 0XFFFFFFFF;                                        /* 找不到空闲block */
    }

    curblock = unusedblock;                                       /* 当前块号等于未使用块编号.下次则从此处开始查找 */

    return unusedblock;                                           /* 返回找到的空闲block */
}

/**
 * @brief       将一个块的数据拷贝到另一块,并且可以写入数据
 * @param       source_pagenum  : 要写入数据的页地址,范围:0~(block_pagenum*block_totalnum-1)
 * @param       colnum          : 要写入的列开始地址(也就是页内地址),范围:0~(page_totalsize-1)
 * @param       pbuffer         : 要写入的数据
 * @param       numbyte_to_write: 要写入的字节数，该值不能超过块内剩余容量大小
 * @retval      0,成功;
 *              其他,失败
 */
uint8_t ftl_copy_and_write_to_block(uint32_t source_pagenum, uint16_t colnum, uint8_t *pbuffer, uint32_t numbyte_to_write)
{
    uint16_t i = 0, temp = 0, wrlen;
    uint32_t source_block = 0, pageoffset = 0;
    uint32_t unusedblock = 0; 
    source_block = source_pagenum / nand_dev.block_pagenum;         /* 获得页所在的块号 */
    pageoffset = source_pagenum % nand_dev.block_pagenum;           /* 获得页在所在块内的偏移 */

retry:
    unusedblock = ftl_find_same_plane_unused_block(source_block);   /* 查找与源块在一个plane的未使用块 */

    if (unusedblock > nand_dev.block_totalnum)
    {
        return 1;                                                   /* 当找到的空余块号大于块总数量的话肯定是出错了 */
    }

    for (i = 0; i < nand_dev.block_pagenum; i++)                    /* 将一个块的数据复制到找到的未使用块中 */
    {
        if (i >= pageoffset && numbyte_to_write)                    /* 数据要写入到当前页 */
        { 
            if (numbyte_to_write > (nand_dev.page_mainsize - colnum)) /* 要写入的数据,超过了当前页的剩余数据 */
            {
                wrlen = nand_dev.page_mainsize - colnum;            /* 写入长度等于当前页剩余数据长度 */
            }
            else 
            {
                wrlen = numbyte_to_write;                           /* 写入全部数据 */
            }

            temp = nand_copypage_withwrite(source_block * nand_dev.block_pagenum + i, unusedblock * nand_dev.block_pagenum + i, colnum, pbuffer, wrlen);
            colnum = 0;                                             /* 列地址归零 */
            pbuffer += wrlen;                                       /* 写地址偏移 */
            numbyte_to_write -= wrlen;                              /* 写入数据减少 */
        }

        else                                                        /* 无数据写入,直接拷贝即可 */
        {
            temp = nand_copypage_withoutwrite(source_block * nand_dev.block_pagenum + i, unusedblock * nand_dev.block_pagenum + i);
        }

        if (temp)                                                   /* 返回值非零,当坏块处理 */
        { 
            ftl_badblock_mark(unusedblock);                         /* 标记为坏块 */
            ftl_create_lut(1);                                      /* 重建LUT表 */
            goto retry;
        }
    }

    if (i == nand_dev.block_pagenum)                                /* 拷贝完成 */
    {
        ftl_used_blockmark(unusedblock);                            /* 标记块已经使用 */
        nand_eraseblock(source_block);                              /* 擦除源块 */
        for (i = 0; i < nand_dev.block_totalnum; i++)               /* 修正LUT表，用unusedblock替换source_block */
        {
            if (nand_dev.lut[i] == source_block)
            {
                nand_dev.lut[i] = unusedblock;
                break;
            }
        }
    }

    return 0;   /* 成功 */
}

/**
 * @brief       逻辑块号转换为物理块号 
 * @param       lbnnum    : 逻辑块编号
 * @retval      物理块编号
 */
uint16_t ftl_lbn_to_pbn(uint32_t lbnnum)
{
    uint16_t pbnno = 0;

    /* 当逻辑块号大于有效块数的时候返回0XFFFF */
    if (lbnnum > nand_dev.valid_blocknum)
    {
        return 0XFFFF;
    }

    pbnno = nand_dev.lut[lbnnum];

    return pbnno;
}

/**
 * @brief       写扇区(支持多扇区写)，FATFS文件系统使用
 * @param       pbuffer     : 要写入的数据
 * @param       sectorno    : 起始扇区号
 * @param       sectorsize  : 扇区大小(不能大于NAND_ECC_SECTOR_SIZE定义的大小,否则会出错!!)
 * @param       sectorcount : 要写入的扇区数量
 * @retval      0,成功;
 *              其他,失败
 */
uint8_t ftl_write_sectors(uint8_t *pbuffer, uint32_t sectorno, uint16_t sectorsize, uint32_t sectorcount)
{
    uint8_t flag = 0;
    uint16_t temp;
    uint32_t i = 0;
    uint16_t wsecs;                 /* 写页大小 */
    uint32_t wlen;                  /* 写入长度 */
    uint32_t lbnno;                 /* 逻辑块号 */
    uint32_t pbnno;                 /* 物理块号 */
    uint32_t phypageno;             /* 物理页号 */
    uint32_t pageoffset;            /* 页内偏移地址 */
    uint32_t blockoffset;           /* 块内偏移地址 */
    uint32_t markdpbn = 0XFFFFFFFF; /* 标记了的物理块编号 */

    for (i = 0; i < sectorcount; i++)
    {
        lbnno = (sectorno+i) / (nand_dev.block_pagenum * (nand_dev.page_mainsize / sectorsize));/* 根据逻辑扇区号和扇区大小计算出逻辑块号 */
        pbnno = ftl_lbn_to_pbn(lbnno);                                                          /* 将逻辑块转换为物理块 */

        if (pbnno >= nand_dev.block_totalnum)
        {
            return 1;                                                                           /* 物理块号大于NAND FLASH的总块数,则失败. */
        }

        blockoffset =((sectorno + i) % (nand_dev.block_pagenum * (nand_dev.page_mainsize / sectorsize))) * sectorsize; /* 计算块内偏移 */
        phypageno = pbnno * nand_dev.block_pagenum + blockoffset / nand_dev.page_mainsize;                             /* 计算出物理页号 */
        pageoffset = blockoffset % nand_dev.page_mainsize;                                                             /* 计算出页内偏移地址  */
        temp = nand_dev.page_mainsize - pageoffset;                                                                    /* page内剩余字节数 */
        temp /= sectorsize;                                                                                            /* 可以连续写入的sector数  */
        wsecs = sectorcount - i;                                                                                       /* 还剩多少个sector要写 */

        if (wsecs >= temp)
        {
            wsecs = temp;                                                               /* 大于可连续写入的sector数,则写入temp个扇区 */
        }

        wlen = wsecs * sectorsize;                                                      /* 每次写wsecs个sector */

        /* 读出写入大小的内容判断是否全为0XFF */
        flag = nand_readpagecomp(phypageno, pageoffset, 0XFFFFFFFF, wlen / 4, &temp);   /* 读一个wlen/4大小个数据,并与0XFFFFFFFF对比 */

        if (flag)
        {
            return 2;                                                   /* 读写错误，坏块 */
        }
        if (temp == (wlen / 4)) 
        {
            flag = nand_writepage(phypageno, pageoffset, pbuffer, wlen);/* 全为0XFF,可以直接写数据 */
        }
        else
        {
            flag = 1;                                                   /* 不全是0XFF,则另作处理 */
        }
        if (flag == 0 && (markdpbn != pbnno))                           /* 全是0XFF,且写入成功,且标记了的物理块与当前物理块不同 */
        {
            flag = ftl_used_blockmark(pbnno);                           /* 标记此块已经使用 */
            markdpbn = pbnno;                                           /* 标记完成,标记块=当前块,防止重复标记 */
        }
        if (flag)                                                       /* 不全为0XFF/标记失败，将数据写到另一个块 */
        {
            temp = ((uint32_t)nand_dev.block_pagenum * nand_dev.page_mainsize - blockoffset) / sectorsize;/* 计算整个block还剩下多少个SECTOR可以写入 */
            wsecs = sectorcount - i;                                    /* 还剩多少个sector要写 */

            if (wsecs >= temp)
            {
                wsecs = temp;                                           /* 大于可连续写入的sector数,则写入temp个扇区 */
            }

            wlen = wsecs * sectorsize;                                  /* 每次写wsecs个sector */
            flag = ftl_copy_and_write_to_block(phypageno, pageoffset, pbuffer, wlen);  /* 拷贝到另外一个block,并写入数据 */
            if (flag)
            {
                return 3;                                               /* 失败 */
            }
        }

        i += wsecs - 1;
        pbuffer += wlen;                                                /* 数据缓冲区指针偏移 */
    }
    return 0;
} 

/**
 * @brief       读扇区(支持多扇区读)，FATFS文件系统使用
 * @param       pbuffer     : 数据缓存区
 * @param       sectorno    : 起始扇区号
 * @param       sectorsize  : 扇区大小
 * @param       sectorcount : 要写入的扇区数量
 * @retval      0,成功;
 *              其他,失败
 */
uint8_t ftl_read_sectors(uint8_t *pbuffer, uint32_t sectorno, uint16_t sectorsize, uint32_t sectorcount)
{
    uint8_t flag = 0;
    uint16_t rsecs;         /* 单次读取页数 */
    uint32_t i = 0;
    uint32_t lbnno;         /* 逻辑块号 */
    uint32_t pbnno;         /* 物理块号 */
    uint32_t phypageno;     /* 物理页号 */
    uint32_t pageoffset;    /* 页内偏移地址 */
    uint32_t blockoffset;   /* 块内偏移地址 */

    for (i = 0; i < sectorcount; i++)
    {
        lbnno = (sectorno + i) / (nand_dev.block_pagenum * (nand_dev.page_mainsize / sectorsize));  /* 根据逻辑扇区号和扇区大小计算出逻辑块号 */
        pbnno = ftl_lbn_to_pbn(lbnno);                                                              /* 将逻辑块转换为物理块 */

        if (pbnno >= nand_dev.block_totalnum)
        {
            return 1;                                                                               /* 物理块号大于NAND FLASH的总块数,则失败. */
        }

        blockoffset = ((sectorno + i) % (nand_dev.block_pagenum * (nand_dev.page_mainsize / sectorsize))) * sectorsize; /* 计算块内偏移 */
        phypageno = pbnno * nand_dev.block_pagenum + blockoffset / nand_dev.page_mainsize;          /* 计算出物理页号 */
        pageoffset = blockoffset%nand_dev.page_mainsize;                                            /* 计算出页内偏移地址 */
        rsecs = (nand_dev.page_mainsize - pageoffset) / sectorsize;                                 /* 计算一次最多可以读取多少页 */

        if (rsecs > (sectorcount - i))
        {
            rsecs = sectorcount - i;                                                                /* 最多不能超过SectorCount-i */
        }
        flag = nand_readpage(phypageno, pageoffset, pbuffer, rsecs * sectorsize);                   /* 读取数据 */

        if (flag == NSTA_ECC1BITERR)                                                                /* 对于1bit ecc错误,可能为坏块 */
        {
            flag = nand_readpage(phypageno, pageoffset, pbuffer, rsecs * sectorsize);               /* 重读数据,再次确认 */

            if (flag == NSTA_ECC1BITERR)
            {
                ftl_copy_and_write_to_block(phypageno, pageoffset, pbuffer, rsecs * sectorsize);    /* 搬运数据 */
                flag = ftl_blockcompare(phypageno / nand_dev.block_pagenum, 0XFFFFFFFF);            /* 全1检查,确认是否为坏块 */

                if (flag == 0)
                {
                    flag = ftl_blockcompare(phypageno / nand_dev.block_pagenum, 0X00);              /* 全0检查,确认是否为坏块 */
                    nand_eraseblock(phypageno / nand_dev.block_pagenum);                            /* 检测完成后,擦除这个块 */
                }

                if (flag)                                                                           /* 全0/全1检查出错,肯定是坏块了. */
                {
                    ftl_badblock_mark(phypageno/nand_dev.block_pagenum);                            /* 标记为坏块 */
                    ftl_create_lut(1);                                                              /* 重建LUT表 */
                }

                flag = 0;
            }
        }

        if (flag == NSTA_ECC2BITERR)
        {
            flag = 0;                       /* 2bit ecc错误,不处理(可能是初次写入数据导致的) */
        }
        if (flag)
        {
            return 2;                       /* 失败 */
        }

        pbuffer += sectorsize * rsecs;      /* 数据缓冲区指针偏移 */
        i += rsecs - 1;
    }
    return 0; 
}

/**
 * @brief       重新创建LUT表
 * @param       mode    : 0,仅检查第一个坏块标记
 *              1,两个坏块标记都要检查(备份区也要检查)
 * @retval      0,成功;其他,失败
 */
uint8_t ftl_create_lut(uint8_t mode)
{
    uint32_t i;
    uint8_t buf[4];
    uint32_t lbnnum = 0;                                             /* 逻辑块号 */

    for (i = 0; i < nand_dev.block_totalnum; i++)                    /* 复位LUT表，初始化为无效值，也就是0XFFFF */
    {
        nand_dev.lut[i] = 0XFFFF;
    }

    nand_dev.good_blocknum = 0;
    for (i = 0; i < nand_dev.block_totalnum; i++)
    {
        nand_readspare(i * nand_dev.block_pagenum, 0, buf, 4);         /* 读取4个字节 */

        if (buf[0] == 0XFF && mode)
        {
            nand_readspare(i * nand_dev.block_pagenum + 1, 0, buf, 1); /* 好块,且需要检查2次坏块标记 */
        }

        if (buf[0] == 0XFF)                                            /* 是好块 */
        { 
            lbnnum = ((uint16_t)buf[3] << 8) + buf[2];                 /* 得到逻辑块编号 */
            if (lbnnum < nand_dev.block_totalnum)                      /* 逻辑块号肯定小于总的块数量 */
            {
                nand_dev.lut[lbnnum] = i;                              /* 更新LUT表，写LBNnum对应的物理块编号 */
            }
            nand_dev.good_blocknum++;
        }
        else
        {
            logd("bad block index:%d\r\n", i);
        }
    } 
    /* LUT表建立完成以后检查有效块个数 */
    for (i = 0; i < nand_dev.block_totalnum; i++)
    {
        if (nand_dev.lut[i] >= nand_dev.block_totalnum)
        {
            nand_dev.valid_blocknum = i;
            break;
        }
    }

    if (nand_dev.valid_blocknum < 100)
    {
        return 2;               /* 有效块数小于100,有问题.需要重新格式化 */
    }

    return 0;                   /* LUT表创建完成 */
}

/**
 * @brief       FTL整个Block与某个数据对比
 * @param       blockx      : block编号
 * @param       cmpval      : 要与之对比的值
 * @retval      0,检查成功,全部相等;
                1,检查失败,有不相等的情况
 */
uint8_t ftl_blockcompare(uint32_t blockx, uint32_t cmpval)
{
    uint8_t res;
    uint16_t i, j, k;

    for (i = 0; i < 3; i++)                             /* 允许3次机会 */
    {
        for (j = 0; j < nand_dev.block_pagenum; j++)
        {
            /* 检查一个page,并与0XFFFFFFFF对比 */
            nand_readpagecomp(blockx * nand_dev.block_pagenum, 0, cmpval, nand_dev.page_mainsize / 4, &k); 

            if (k != (nand_dev.page_mainsize / 4))break;
        }

        if (j == nand_dev.block_pagenum)
        {
            return 0;                                   /* 检查合格,直接退出 */
        }

        res = nand_eraseblock(blockx);

        if (res)
        {
            logd("error erase block:%d\r\n", i);
        }
        else
        { 
            if (cmpval != 0XFFFFFFFF)                   /* 不是判断全1,则需要重写数据 */
            {
                for (k = 0; k < nand_dev.block_pagenum; k++)
                {
                    nand_write_pageconst(blockx * nand_dev.block_pagenum + k, 0, 0, nand_dev.page_mainsize / 4); /* 写PAGE */
                }
            }
        }
    }

    logd("bad block checked:%d\r\n", blockx);
    return 1;
}

/**
 * @brief       FTL初始化时，搜寻所有坏块,使用:擦-写-读 方式
 * @note        512M的NAND ,需要约3分钟时间,来完成检测
 *              对于RGB屏,由于频繁读写NAND,会引起屏幕乱闪
 * @param       无
 * @retval      好块的数量
 */
uint32_t ftl_search_badblock(void)
{
    uint8_t *blktbl;
    uint8_t res;
    uint32_t i, j; 
    uint32_t goodblock = 0;

    blktbl = unios_malloc(nand_dev.block_totalnum);  /* 申请block坏块表内存,对应项:0,好块;1,坏块; */
    nand_erasechip();                                    /* 全片擦除 */

    for (i = 0; i < nand_dev.block_totalnum; i++)        /* 第一阶段检查,检查全1 */
    {
        res = ftl_blockcompare(i, 0XFFFFFFFF);           /* 全1检查 */

        if (res)
        {
            blktbl[i] = 1;                               /* 坏块  */
        }
        else
        { 
            blktbl[i] = 0;/* 好块 */

            for (j = 0; j < nand_dev.block_pagenum; j++) /* 写block为全0,为后面的检查准备 */
            {
                nand_write_pageconst(i * nand_dev.block_pagenum + j, 0, 0, nand_dev.page_mainsize / 4);
            } 
        }
    }

    for (i = 0; i < nand_dev.block_totalnum; i++)        /* 第二阶段检查,检查全0 */
    { 
        if (blktbl[i] == 0)                              /* 在第一阶段,没有被标记坏块的,才可能是好块 */
        {
            res = ftl_blockcompare(i, 0);                /* 全0检查 */

            if (res)
            {
                blktbl[i] = 1;                           /* 标记坏块 */
            }
            else
            {
                goodblock++; 
            }
        }
    }

    nand_erasechip();                                    /* 全片擦除 */

    for (i = 0; i < nand_dev.block_totalnum; i++)        /* 第三阶段检查,标记坏块 */
    { 
        if (blktbl[i])
        {
            ftl_badblock_mark(i);                        /* 是坏块 */
        }
    }

    return goodblock;                                    /* 返回好块的数量 */
}

/**
 * @brief       格式化NAND 重建LUT表
 * @param       无
 * @retval      0,成功;
                其他,失败
 */
uint8_t ftl_format(void)
{
    uint8_t temp;
    uint32_t i, n;
    uint32_t goodblock = 0;
    nand_dev.good_blocknum = 0;

#if FTL_USE_BAD_BLOCK_SEARCH == 1                    /* 使用擦-写-读的方式,检测坏块 */
    nand_dev.good_blocknum = FTL_SearchBadBlock();   /* 搜寻坏块.耗时很久 */
#else                                                /* 直接使用NAND FLASH的出厂坏块标志(其他块,默认是好块) */

    for (i = 0; i < nand_dev.block_totalnum; i++)
    {
        temp = ftl_check_badblock(i);                /* 检查一个块是否为坏块 */

        if (temp == 0)                               /* 好块 */
        {
            temp = nand_eraseblock(i);

            if (temp)                                /* 擦除失败,认为坏块 */
            {
                logd("Bad block:%d\r\n", i);
                ftl_badblock_mark(i);                /* 标记是坏块 */
            }
            else
            {
                nand_dev.good_blocknum++;            /* 好块数量加一 */
            }
        }
    }
#endif
    logd("good_blocknum:%d\r\n", nand_dev.good_blocknum);

    if (nand_dev.good_blocknum < 100)
    {
        return 1;                                    /*如果好块的数量少于100，则NAND Flash报废 */
    }

    goodblock = (nand_dev.good_blocknum * 93) / 100; /* %93的好块用于存储数据 */

    n = 0;

    for (i = 0; i < nand_dev.block_totalnum; i++)    /* 在好块中标记上逻辑块信息 */
    {
        temp = ftl_check_badblock(i);                /* 检查一个块是否为坏块 */

        if (temp == 0)                               /* 好块 */
        { 
            nand_writespare(i * nand_dev.block_pagenum, 2, (uint8_t*)&n, 2);  /* 写入逻辑块编号 */
            n++;                                     /* 逻辑块编号加1 */

            if (n == goodblock) break;               /* 全部标记完了 */
        }
    } 
    if (ftl_create_lut(1))
    {
        return 2;                                    /* 重建LUT表失败 */
    }

    return 0;
}



DSTATUS ffdisk_nand_init(ffdisk_t *disk) {
    if(disk->initialized) return RES_OK;
    uint8_t ret = ftl_init();
    if(ret) return RES_ERROR;
    disk->initialized = true;
    return RES_OK;
}

DSTATUS ffdisk_nand_status(struct _ffdisk *disk) {
    return RES_OK;
}

DRESULT ffdisk_nand_read(struct _ffdisk *disk, BYTE *buff, LBA_t sector, UINT count) {
    return ftl_read_sectors(buff, sector, 512, count) ? RES_ERROR : RES_OK;
}

DRESULT ffdisk_nand_write(struct _ffdisk *disk, const BYTE *buff, LBA_t sector, UINT count) {
    return ftl_write_sectors((uint8_t *)buff, sector, 512, count) ? RES_ERROR : RES_OK;
}

DRESULT ffdisk_nand_ioctl(struct _ffdisk *disk, BYTE cmd, void *buff) {
    switch (cmd) {
        case CTRL_SYNC: return RES_OK;
        
        case GET_SECTOR_SIZE:
            *(WORD*)buff = 512;       /* NAND FLASH扇区强制为 512 字节大小 */
            return RES_OK;

        case GET_BLOCK_SIZE:
            *(WORD*)buff = nand_dev.page_mainsize / 512;    /* block 大小，定义成一个page的大小 */
            return RES_OK;

        case GET_SECTOR_COUNT:
            *(DWORD *)buff = nand_dev.valid_blocknum * nand_dev.block_pagenum * nand_dev.page_mainsize / 512;    /* NAND FLASH的总扇区大小 */
            return RES_OK;

        default: return RES_PARERR;
    }
}
